package main

import (
	"fmt"
	"github.com/kardianos/service"
	"gopkg.in/ini.v1"
	"io"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
)

var logFile *os.File

var runConfig = new(RunConfig)

func main() {

	loadConfig()

	config := &service.Config{
		Name:        runConfig.AppTitle,
		DisplayName: runConfig.AppTitle,
		Description: runConfig.AppDescribe,
	}

	var pg = &Program{
		stopped: false,
	}

	s, err := service.New(pg, config)
	if err != nil {
		log.Fatal(err)
	}

	if len(os.Args) > 1 {
		err = service.Control(s, os.Args[1])
		if err != nil {
			log.Fatal(err)
		}
		return
	}

	//  设置窗口标题
	SetConsoleTitle(runConfig.AppTitle)

	err = s.Run()
	if err != nil {
		log.Fatalln(err)
	}
}

type RunConfig struct {
	WorkDir string
	LogFile string

	AutoStartWhenExist bool
	AutoStartDelay     int

	AppTitle    string
	AppDescribe string
	AppDir      string

	JavaCommand        string
	JvmArgs            string
	Encoding           string
	ClassPath          []string `ini:",,allowshadow"`
	Env                []string `ini:",,allowshadow"`
	MaxMem             string
	MinMem             string
	MainClass          string
	TmpDir             string
	RemoteDebugAddress string
	ApplicationArgs    string
}

func generateCommand(runConfig *RunConfig) string {
	var args []string

	// remoteDebug
	if runConfig.RemoteDebugAddress != "" {
		args = append(args, fmt.Sprintf("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=%s", runConfig.RemoteDebugAddress))
	}

	// encoding
	args = append(args, fmt.Sprintf("-Dfile.encoding=%s", runConfig.Encoding))

	// tmpdir
	if runConfig.TmpDir != "" {
		args = append(args, fmt.Sprintf("-Djava.io.tmpdir=%s", runConfig.TmpDir))
	}

	// 其他JVM 参数
	if runConfig.JvmArgs != "" {
		args = append(args, runConfig.JvmArgs)
	}

	// classpath
	args = append(args, "-classpath", generateClassPath(runConfig))

	// max min mem
	args = append(args, fmt.Sprintf("-Xms%s -Xmx%s", runConfig.MinMem, runConfig.MaxMem))

	// mainClass
	args = append(args, runConfig.MainClass)

	// mainClass
	args = append(args, runConfig.ApplicationArgs)

	// 生成java命令
	fullExec, err := exec.LookPath(runConfig.JavaCommand)
	if err == nil {
		runConfig.JavaCommand = fullExec
	}
	cmdLine := fmt.Sprintf("%s %s", runConfig.JavaCommand, strings.Join(args, " "))

	return cmdLine
}

func generateClassPath(runConfig *RunConfig) string {
	var cps []string
	for _, cp := range runConfig.ClassPath {
		cp = cleanPath(cp)
		if isAbsPath(cp) {
			cps = append(cps, cp)
		} else {
			cps = append(cps, path.Join(runConfig.AppDir, cp))
		}
	}
	return strings.Join(cps, ";")
}

func cleanPath(cp string) string {
	cp = path.Clean(cp)
	cp = strings.ReplaceAll(cp, "\\", "/")
	return cp
}

func isAbsPath(ps string) bool {
	if "windows" == runtime.GOOS {
		return strings.ContainsRune(ps, ':')
	}
	return path.IsAbs(ps)
}

func checkErr(err error, msg string) {
	if err != nil {
		log.Fatalln(msg + " " + err.Error())
	}
}

func SplitCommand(cmdLine string) (cmd string, args []string) {
	cmdLine = strings.TrimSpace(cmdLine)
	reg := regexp.MustCompile("\\s+")
	cmdLine = reg.ReplaceAllString(cmdLine, " ")
	parts := strings.Split(cmdLine, " ")
	return parts[0], parts[1:]
}

func loadConfig() {
	configPath := getConfigPath()

	cfg, err := ini.ShadowLoad(configPath)
	checkErr(err, "加载配置文件失败")

	section, err := cfg.GetSection("app")
	checkErr(err, "加载配置文件失败")

	err = section.MapTo(runConfig)
	checkErr(err, "加载配置文件失败")

	// 修正工作目录
	execPath, _ := getExecPath()
	if !isAbsPath(runConfig.WorkDir) {
		runConfig.WorkDir = filepath.Clean(filepath.Join(execPath, runConfig.WorkDir))
	}
	if !isAbsPath(runConfig.AppDir) {
		runConfig.AppDir = filepath.Clean(filepath.Join(runConfig.WorkDir, runConfig.AppDir))
	}

	if runConfig.TmpDir == "" {
		runConfig.TmpDir = os.TempDir()
	}

	logFile = getLogFile(runConfig)
	log.SetOutput(io.MultiWriter(logFile, os.Stdout))
	log.SetFlags(log.LstdFlags | log.Lshortfile | log.LUTC)
}

func getExecPath() (execPath string, execName string) {
	execPath, err := os.Executable()
	checkErr(err, "获取目录失败")
	_, execName = filepath.Split(execPath)
	ext := filepath.Ext(execName)
	execName = execName[:len(execName)-len(ext)]
	execPath = filepath.Dir(execPath)
	return
}

func getConfigPath() string {
	execPath, _ := getExecPath()

	configPath := filepath.Join(execPath, "config.ini")
	if !isFileExist(configPath) {
		configPath = "config.ini"
	}
	return configPath
}

func isFileExist(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	}
	if os.IsNotExist(err) {
		return false
	}
	return true
}

func getLogFile(config *RunConfig) *os.File {
	logfile := path.Join(config.WorkDir, config.LogFile)
	logFile, err := os.OpenFile(logfile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766)
	checkErr(err, "日志文件创建失败")
	return logFile
}
